GLSL

  • I haven't studied much about it. There are a lot of texts here that need to be revised and properly rewritten.

Where I Stopped

About

  • GLSL  (OpenGL Shader Language).

  • Open standard (Khronos Group).

  • Works with :

    • OpenGL.

    • Vulkan.

    • WebGL.

  • How Vulkan uses it :

    • It's the most common shader language used for Vulkan.

    • It's human-readable, widely supported, and tools like glslangValidator  convert it to SPIR-V easily.

    • Compiling GLSL to SPIR-V:

      • The output is a binary SPIR-V files ( vert.spv , frag.spv ) fed to Vulkan at runtime.

      glslangValidator -V shader.vert -o vert.spv
      glslangValidator -V shader.frag -o frag.spv
      

Formats

  • every format that does not end in _u/s norm , _u/s scaled , or _*float/int  are storing non-linear colors

  • reads and writes through the texture interface will  have conversion to and from linear done for you

  • A raw memory access, e.g. through a buffer or a copy, does not.

  • So, if the uniform sampler2D  is SRGB, I will read it as linear inside the shader? Also, if the color attachment is SRGB, the output will be converted from linear to srgb?

    • only if you are accessing it through the texture/image interface, yes.

Constant Data

  • This data can be anything we want it to be, for instance it can be used for things such as calculating where an object should be placed inside our world, or computing the overall brightness of an object based on the lights in the scene.

  • The data remains constant  across every shader invocation of a draw call.

    • It differs from other data (e.g. input vertex data) in this sense.

  • The data can be shared  between shader stages, as we know it isn’t going to be changed throughout the runtime of a single draw call in a render pipeline.

  • Constant data is implemented in shader code by using global variables.

  • Global variables  have the following format: <layout> <storage> <type> <variable_name> .

layout(location = 0) in vec4 position;

layout(set = 0, binding = 0) uniform Data
{
    mat4 model;
} data;

layout(location = 0) out vec4 o_pos;

Varying Types

  • The global variables that use inputs ( in ) and outputs ( out ) are values that may vary  from one shader invocation to the next, therefore they shouldn’t  be used for constant data.

  • They require a layout location  which is used to identify a particular input/output.

  • They have slightly different rules for what they do depending on the shader stage, and have slightly different restrictions on the types of data it can represent. However, generally their use is to feed values from one stage to the next (e.g. from vertex shader to fragment shader).

Uniform Types

  • Uniform types are global variables that have either the uniform  or buffer  storage type, these are uniform buffer objects  and shader storage buffer objects  respectively. They describe data which remains constant across an entire draw call, meaning that the values stay the same across the different shader stages and shader invocations.

  • These values use a layout binding  and, when working with multiple VkDescriptorSet s, we will also give it a layout set

  • Uniform buffer objects (UBOs)

    • Are the more commonly used of the two. They are read-only  buffers, so trying to edit them in shader code will result in a compile-time error.

  • Shader storage buffer objects (SSBOs)

    • Are like special types of uniform buffer objects, denoted by the storage type buffer . Unlike UBOs they can be written to, meaning the values can  be changed in the shaders so therefore they don’t always represent data that is constant.

    • Having said this, depending on the implementation, they generally can hold a lot more data as opposed to UBOs.

      • To check how much data we can store in uniform buffers and storage buffers, you can query the physical device for its VkPhysicalDeviceLimits  and check the values maxUniformBufferRange  and maxStorageBufferRange  respectively.

Cheat Sheet

  • gl_Position

    • Is always in clip space  (homogeneous coordinates before perspective divide).

    • After the GPU does the perspective divide ( gl_Position.xyz / gl_Position.w ), you get Normalized Device Coordinates (NDC)  in the range [-1, 1] .

    • The viewport transform then maps NDC into window coordinates  (pixels on screen).

  • Model Space -> World Space -> View Space :

vec4 vertex_pos_world_space = model.model_matrix * vec4(vertex_pos, 1.0);
vec4 vertex_pos_view_space  = globals.view * vertex_pos_world_space;
gl_Position  = globals.proj * vertex_pos_view_space;
  • SCREEN UV :

vec2 screen_uv = gl_FragCoord.xy / vec2(viewport_size);

// Godot
vec2 screen_uv = gl_FragCoord.xy * screen_pixel_size;
vec2 viewport_resolution = vec2(1920, 1080);
vec2 screen_space_uv = gl_FragCoord.xy / viewport_resolution.xy;
  • <excalidraw_not_loaded> .

  • Vertex Light :

#ifdef LIGHT_VERTEX_USED
vec3 light_vertex = vertex;
Enums
#define GLOW_MODE_ADD 0
#define GLOW_MODE_SCREEN 1
#define GLOW_MODE_SOFTLIGHT 2
#define GLOW_MODE_REPLACE 3
#define GLOW_MODE_MIX 4

if (params.glow_mode == GLOW_MODE_ADD) {
Bitmasks
#define FLAG_USE_BCS (1 << 0)
#define FLAG_USE_GLOW (1 << 1)
#define FLAG_USE_AUTO_EXPOSURE (1 << 2)
#define FLAG_USE_COLOR_CORRECTION (1 << 3)
#define FLAG_USE_FXAA (1 << 4)
#define FLAG_USE_DEBANDING (1 << 5)
#define FLAG_CONVERT_TO_SRGB (1 << 6)

if (bool(params.flags & FLAG_USE_BCS)) {
    color.rgb = apply_bcs(color.rgb, params.bcs);
}

Basic

Comments
//
/*
*/
Source Strings
float f\
oo;
// forms a single line equivalent to "float foo;"
// (assuming '\' is the last character before the new-line and "oo" are
// the first two characters of the next line)

Preprocessors

#  
#define  
#undef  

#if  
#ifdef  
#ifndef  
#else  
#elif  
#endif  

#error  
#pragma  

#extension  
#version  

#line

defined
##
  • #if , ** #ifdef,** #ifndef , #else , #elif , and #endif`.

    • Operate as is standard for C++ preprocessors, except for the following:

      • Expressions following #if  and #elif  are further restricted to expressions operating on literal integer constants, plus identifiers consumed by the defined  operator.

      • Character constants are not supported.

  • #pragma

    • Allows implementation-dependent compiler control. Tokens following #pragma  are not subject to preprocessor macro expansion. If an implementation does not recognize the tokens following #pragma , then it will ignore that pragma.

    • The following pragmas are defined as part of the language.

    • The STDGL  pragma is used to reserve pragmas for use by future revisions of this language. No implementation may use a pragma whose first token is STDGL .

      #pragma STDGL
      
    • Can be used to turn off optimizations as an aid in developing and debugging shaders. It can only be used outside function definitions. By default, optimization is turned on for all shaders.

      #pragma optimize(on)
      #pragma optimize(off)
      
    • Can be used to enable compiling and annotating a shader with debug information, so that it can be used with a debugger. It can only be used outside function definitions. By default, debug is turned off.

    #pragma debug(on)
    #pragma debug(off)
    
  • #version

    • Shaders should declare the version of the language they are written to.

    #version number profile_opt
    
    • Profile arguments:

      core
      compatibility
      es
      
      • The default is core , if none is provided.

  • #error

    • will cause the implementation to put a compile-time diagnostic message into the shader object’s information log (see section 7.12 “Shader, Program and Program Pipeline Queries” of the OpenGL Specification  for how to access a shader object’s information log). The message will be the tokens following the #error  directive, up to the first new-line. The implementation must treat the presence of a #error  directive as a compile-time error.

  • defined

    defined identifier
    defined ( identifier )
    
    • Two tokens in a macro can be concatenated into one token using the token pasting ( ## ) operator, as is standard for C++ preprocessors.

  • #extension

    • Directives to control the behavior of the compiler with respect to extensions are declared with:

    #extension extension_name : behavior
    #extension all : behavior
    
    • where extension_name  is the name of an extension. Extension names are not documented in this specification. The token all  means the behavior applies to all extensions supported by the compiler.

Predefined macros
  • All macro names containing two consecutive underscores ( __ ) are reserved for use by underlying software layers. Defining or undefining such a name in a shader does not itself result in an error, but may result in unintended behaviors that stem from having multiple definitions of the same name.

    • __LINE__

      • Will substitute a decimal integer constant that is one more than the number of preceding new-lines in the current source string.

    • __FILE__

      • Will substitute a decimal integer constant that says which source string number is currently being processed.

    • __VERSION__

      • Will substitute a decimal integer reflecting the version number of the OpenGL Shading Language. The version of the shading language described in this document will have __VERSION__  substitute the decimal integer 460.

  • All macro names prefixed with GL_  are also reserved, and defining or undefining such a name results in a compile-time error.

Basic Types

Transparent Types

| Type        | Meaning                                                            |
| ----------- | ------------------------------------------------------------------ |
| void     | for functions that do not return a value                           |
| bool     | a conditional type, taking on values of true or false              |
| int      | a signed integer                                                   |
| uint     | an unsigned integer                                                |
| float    | a single-precision floating-point scalar                           |
| double   | a double-precision floating-point scalar                           |
| vec2     | a two-component single-precision floating-point vector             |
| vec3     | a three-component single-precision floating-point vector           |
| vec4     | a four-component single-precision floating-point vector            |
| dvec2    | a two-component double-precision floating-point vector             |
| dvec3    | a three-component double-precision floating-point vector           |
| dvec4    | a four-component double-precision floating-point vector            |
| bvec2    | a two-component Boolean vector                                     |
| bvec3    | a three-component Boolean vector                                   |
| bvec4    | a four-component Boolean vector                                    |
| ivec2    | a two-component signed integer vector                              |
| ivec3    | a three-component signed integer vector                            |
| ivec4    | a four-component signed integer vector                             |
| uvec2    | a two-component unsigned integer vector                            |
| uvec3    | a three-component unsigned integer vector                          |
| uvec4    | a four-component unsigned integer vector                           |
| mat2     | a 2 Ă— 2 single-precision floating-point matrix                     |
| mat3     | a 3 Ă— 3 single-precision floating-point matrix                     |
| mat4     | a 4 Ă— 4 single-precision floating-point matrix                     |
| mat2x2   | same as a mat2                                                  |
| mat2x3   | a single-precision floating-point matrix with 2 columns and 3 rows |
| mat2x4   | a single-precision floating-point matrix with 2 columns and 4 rows |
| mat3x2   | a single-precision floating-point matrix with 3 columns and 2 rows |
| mat3x3   | same as a mat3                                                  |
| mat3x4   | a single-precision floating-point matrix with 3 columns and 4 rows |
| mat4x2   | a single-precision floating-point matrix with 4 columns and 2 rows |
| mat4x3   | a single-precision floating-point matrix with 4 columns and 3 rows |
| mat4x4   | same as a mat4                                                  |
| dmat2    | a 2 Ă— 2 double-precision floating-point matrix                     |
| dmat3    | a 3 Ă— 3 double-precision floating-point matrix                     |
| dmat4    | a 4 Ă— 4 double-precision floating-point matrix                     |
| dmat2x2  | same as a dmat2                                                 |
| dmat2x3  | a double-precision floating-point matrix with 2 columns and 3 rows |
| dmat2x4  | a double-precision floating-point matrix with 2 columns and 4 rows |
| dmat3x2  | a double-precision floating-point matrix with 3 columns and 2 rows |
| dmat3x3  | same as a dmat3                                                 |
| dmat3x4  | a double-precision floating-point matrix with 3 columns and 4 rows |
| dmat4x2  | a double-precision floating-point matrix with 4 columns and 2 rows |
| dmat4x3  | a double-precision floating-point matrix with 4 columns and 3 rows |
| dmat4x4  | same as a dmat4                                                 |

Floating-Point Opaque Types

|Type|Meaning|
|---|---|
| sampler1D  
texture1D  
image1D |a handle for accessing a 1D texture|
| sampler1DShadow |a handle for accessing a 1D depth texture with comparison|
| sampler1DArray  
texture1DArray  
image1DArray |a handle for accessing a 1D array texture|
| sampler1DArrayShadow |a handle for accessing a 1D array depth texture with comparison|
| sampler2D  
texture2D  
image2D |a handle for accessing a 2D texture|
| sampler2DShadow |a handle for accessing a 2D depth texture with comparison|
| sampler2DArray  
texture2DArray  
image2DArray |a handle for accessing a 2D array texture|
| sampler2DArrayShadow |a handle for accessing a 2D array depth texture with comparison|
| sampler2DMS  
texture2DMS  
image2DMS |a handle for accessing a 2D multisample texture|
| sampler2DMSArray  
texture2DMSArray  
image2DMSArray |a handle for accessing a 2D multisample array texture|
| sampler2DRect  
texture2DRect  
image2DRect |a handle for accessing a rectangle texture|
| sampler2DRectShadow |a handle for accessing a rectangle texture with comparison|
| sampler3D  
texture3D  
image3D |a handle for accessing a 3D texture|
| samplerCube  
textureCube  
imageCube |a handle for accessing a cube mapped texture|
| samplerCubeShadow |a handle for accessing a cube map depth texture with comparison|
| samplerCubeArray  
textureCubeArray  
imageCubeArray |a handle for accessing a cube map array texture|
| samplerCubeArrayShadow |a handle for accessing a cube map array depth texture with comparison|
| samplerBuffer  
textureBuffer  
imageBuffer |a handle for accessing a buffer texture|
| subpassInput |a handle for accessing a floating-point subpass input|
| subpassInputMS |a handle for accessing a multi-sampled floating-point subpass input|

Signed Integer Opaque Types

|Type|Meaning|
|---|---|
| isampler1D  
itexture1D  
iimage1D |a handle for accessing an integer 1D texture|
| isampler1DArray  
itexture1DArray  
iimage1DArray |a handle for accessing an integer 1D array texture|
| isampler2D  
itexture2D  
iimage2D |a handle for accessing an integer 2D texture|
| isampler2DArray  
itexture2DArray  
iimage2DArray |a handle for accessing an integer 2D array texture|
| isampler2DMS  
itexture2DMS  
iimage2DMS |a handle for accessing an integer 2D multisample texture|
| isampler2DMSArray  
itexture2DMSArray  
iimage2DMSArray |a handle for accessing an integer 2D multisample array texture|
| isampler2DRect  
itexture2DRect  
iimage2DRect |a handle for accessing an integer 2D rectangle texture|
| isampler3D  
itexture3D  
iimage3D |a handle for accessing an integer 3D texture|
| isamplerCube  
itextureCube  
iimageCube |a handle for accessing an integer cube mapped texture|
| isamplerCubeArray  
itextureCubeArray  
iimageCubeArray |a handle for accessing an integer cube map array texture|
| isamplerBuffer  
itextureBuffer  
iimageBuffer |a handle for accessing an integer buffer texture|
| isubpassInput |a handle for accessing an integer subpass input|
| isubpassInputMS |a handle for accessing a multi-sampled integer subpass input|

Unsigned Integer Opaque Types

| Type                                                                      | Meaning                                                                 |
| ------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| usampler1D  
utexture1D  
uimage1D                       | a handle for accessing an unsigned integer 1D texture                   |
| usampler1DArray  
utexture1DArray  
uimage1DArray        | a handle for accessing an unsigned integer 1D array texture             |
| usampler2D  
utexture2D  
uimage2D                       | a handle for accessing an unsigned integer 2D texture                   |
| usampler2DArray  
utexture2DArray  
uimage2DArray        | a handle for accessing an unsigned integer 2D array texture             |
| usampler2DMS  
utexture2DMS  
uimage2DMS                 | a handle for accessing an unsigned integer 2D multisample texture       |
| usampler2DMSArray  
utexture2DMSArray  
uimage2DMSArray  | a handle for accessing an unsigned integer 2D multisample array texture |
| usampler2DRect  
utexture2DRect  
uimage2DRect           | a handle for accessing an unsigned integer rectangle texture            |
| usampler3D  
utexture3D  
uimage3D                       | a handle for accessing an unsigned integer 3D texture                   |
| usamplerCube  
utextureCube  
uimageCube                 | a handle for accessing an unsigned integer cube mapped texture          |
| usamplerCubeArray  
utextureCubeArray  
uimageCubeArray  | a handle for accessing an unsigned integer cube map array texture       |
| usamplerBuffer  
utextureBuffer  
uimageBuffer           | a handle for accessing an unsigned integer buffer texture               |
| atomic_uint                                                            | a handle for accessing an unsigned integer atomic counter               |
| usubpassInput                                                          | a handle for accessing an unsigned-integer subpass input                |
| usubpassInputMS                                                        | a handle for accessing a multi-sampled unsigned-integer subpass input   |

Sampler Opaque Types

| Type              | Meaning                                                                               |
| ----------------- | ------------------------------------------------------------------------------------- |
| sampler        | a handle for accessing state describing how to sample a texture                       |
| samplerShadow  | a handle for accessing state describing how to sample a depth texture with comparison |

Pointers
  • There are no pointer types.

Integers
  • For OpenGL:

    • unsigned integers have exactly 32 bits of precision.

    • signed integers use 32 bits, including a sign bit, in two’s complement form.

  • For Vulkan:

    • mediump  and lowp  integers are as defined by the SPIR-V RelaxedPrecision  decoration.

    • highp  unsigned integers have exactly 32 bits of precision.

    • highp  signed integers use 32 bits, including a sign bit, in two’s complement form.

Floats
float a, b = 1.5;    // single-precision floating-point
double c, d = 2.0LF; // double-precision floating-point
  • When the suffix lf  or LF  is present, the literal has type double . Otherwise, the literal has type float .

  • A decimal point ( . ) is not needed if the exponent part is present.

Vectors
vec2 texcoord1, texcoord2;
vec3 position;
vec4 myRGBA;
ivec2 textureLookup;
bvec3 less;
Matrices
  • Matrix types beginning with " mat " have single-precision components while matrix types beginning with " dmat " have double-precision components.

  • The first number in the type is the number of columns , the second is the number of rows .

  • If there is only one number, the matrix is square.

mat2 mat2D;
mat3 optMatrix;
mat4 view, projection;
mat4x4 view; // an alternate way of declaring a mat4
mat3x2 m;    // a matrix with 3 columns and 2 rows
dmat4 highPrecisionMVP;
dmat2x4 dm;
Atomic Counters
  • OpenGL:

    • Atomic counter types (e.g. atomic_uint ) are opaque handles to counters, declared and behaving as described above for opaque types. The variables they declare specify which counter to access when using the built-in atomic counter functions as described in “ Atomic Counter Functions ”. They are bound to buffers as described in “ Atomic Counter Layout Qualifiers ”.

    • Members of structures cannot be declared as atomic counter types.

  • Vulkan:

    • Atomic counter types are not available when targeting Vulkan.

Arrays

float frequencies[3];

uniform vec4 lightPosition[4u];

light lights[];            // Unsized. Valid in GLSL, illegal in ESSL.

const int numLights = 2;
light lights[numLights];

vec4 a[3][2];

// a shader storage block, introduced in section 4.3.7 "Buffer Variables"
buffer b {
    float u[]; // an error, unless u gets statically sized by link time
    vec4 v[];  // okay, v will be sized dynamically, if not statically
} name[3];     // when the block is arrayed, all u will be the same size, but not necessarily all v, if sized dynamically
  • Arrays only have a single dimension (a single number within “[ ]”), however, arrays of arrays can be declared.

  • Any type can be formed into an array.

  • All arrays are inherently homogeneous; made of elements all having the same type and size, with one exception. An array of shader storage blocks whose last member is a runtime-sized array allows the individual blocks to have different sizes and hence a different number of elements in the trailing array.

  • Explicitly Sized

    • The number of elements in the array is explicitly given.

    • It is a compile-time error if:

      • an explicitly sized array is indexed with a constant expression greater than or equal to the declared size.

  • Runtime Sized

    • The number of elements is not given and the array is the outermost dimension of the last declared member of a shader storage block (see section “ Interface Blocks ”). The array size is inferred at run-time from the size of the data store backing the shader storage block.

    • It is a compile-time error if:

      • a runtime array is passed as a function argument.

  • Unsized

    • The number of array elements is not given and the array is not runtime sized.

    • Unsized arrays may become explicitly sized following either an explicitly-sized initializer or a redeclaration with an explicit size (Explicitly-sized and runtime-sized arrays may not be redeclared). It is a compile-time error to redeclare an array with a different underlying member type.

    • It is a compile-time error if:

      • an unsized array is indexed with anything other than a constant integral expression.

      • an unsized array is declared as a formal parameter to a function.

      • an unsized array is declared as the return type of a function.

      • an unsized array is passed as a function argument.

      • an unsized array is redeclared with a size less than or equal to any constant index used earlier in the shader to index the array.

  • Undefined behavior results from indexing an array with a non-constant expression that’s greater than or equal to the array’s size or less than 0.

  • It is a compile-time error to assign either to or from a runtime-sized or unsized array (rather than specific elements). Note, this is a rare case that initializers and assignments appear to have different semantics. An initializer for an unsized array is valid and will size the array, but the equivalent assigment is not valid. For example,

float a[5];
float b[];
// An initializer sizes an array ...
float c[] = a;  // c is explicitly size 5
// ... but the equivalent assignment is not valid
float d[];
d = a;          // Error. Assignment to an unsized array
// It is never valid to assign from an unsized array
float e[] = b;  // Error. b is unsized so cannot be assigned
  • Alternatively, the initializer-list syntax can be used to initialize an array of arrays:

vec4 a[3][2] = { vec4[2](vec4(0.0), vec4(1.0)),
                 vec4[2](vec4(0.0), vec4(1.0)),
                 vec4[2](vec4(0.0), vec4(1.0)) };
  • For arrays of arrays, any unsized dimension is explicitly sized by the initializer:

vec4 a[][] = { vec4[2](vec4(0.0), vec4(1.0)), // okay, size to a[3][2]
               vec4[2](vec4(0.0), vec4(1.0)),
               vec4[2](vec4(0.0), vec4(1.0)) };
Array Type
  • An array type can be formed by specifying a non-array type

float[5]    // an array of size [5] of float
float[2][3] // an array of size [2] of array of size [3] of float,
            // not size [3] of float[2]
  • Such an array type can be used anywhere any other type can be used, including as the return value from a function, as a constructor of an array and in declarations.

// As a function return type
float[5] foo() { }
// As an array constructor
float[5](3.4, 4.2, 5.0, 5.2, 1.1)
// In declaring an unnamed parameter
void foo(float[5])
// In normal declarations
float[5] a;

// The following 3 declarations are equivalent:
vec4 a[3][2]; // size-3 array of size-2 array of vec4
vec4[2] a[3];
vec4[3][2] a;
  • If such an array type is unsized and used as a constructor then the size of the array is inferred from the constructor arguments. For example,

float a[5] = float[5](3.4, 4.2, 5.0, 5.2, 1.1);
float a[5] = float[](3.4, 4.2, 5.0, 5.2, 1.1);  // Constructor also of type float[5]
Length Method
  • The return value has type int .

float a[5];
a.length(); // returns 5

vec4 a[3][2];
a.length()    // returns 3
a[x].length() // returns 2
  • It is a compile-time error to use the length()  method on an unsized array. The return value is a constant expression if and only if the array is explicitly-sized.

Structs

struct light {
    float intensity;
    vec3 position;
} lightVar;
  • In this example, light  becomes the name of the new type, and lightVar  becomes a variable of type light .

  • To declare variables of the new type, use its name (without the keyword struct ).

light lightVar2;
  • Comptime errors:

struct S { float f; }; // Allowed: S is defined as a structure.

struct T {
    S;              // Error: anonymous structures disallowed
    struct { ... }; // Error: embedded structures disallowed
    S s;            // Allowed: nested structure with a name.
};

Functions

  • Functions that do not return a value must be declared as void .

    • There is no default function return type.

    • The keyword void  cannot be used in any other declarations (except for empty formal or actual parameter lists), or a compile-time error results.

Storage Qualifiers

  • Local variables can only use the const  storage qualifier (or use no storage qualifier).

  • Note that function parameters can use const , in , and out  qualifiers, but as parameter qualifiers .

  • Function return types and structure members do not use storage qualifiers.

  • Initializers in global declarations may only be used in declarations of global variables with no storage qualifier, with a const  qualifier, or with a uniform  qualifier. Global variables without storage qualifiers that are not initialized in their declaration or by the application will not be initialized, but rather will enter main()  with undefined values.

<none: default>
  • If no qualifier is present on a global variable, then the variable has no linkage to the application or shaders running on other pipeline stages.

  • For either global or local unqualified variables, the declaration will appear to allocate memory associated with the processor it targets.

  • This variable will provide read/write access to this allocated memory.

const
  • a variable whose value cannot be changed.

  • Named compile-time constants or read-only variables can be declared using the const  qualifier. The const  qualifier can be used with any of the non-void transparent basic data types, as well as with structures and arrays of these. It is a compile-time error to write to a const  variable outside of its declaration, so they must be initialized when declared. For example,

const vec3 zAxis = vec3 (0.0, 0.0, 1.0);
const float ceiling = a + b; // a and b not necessarily constants
  • Structure members may not be qualified with const . Structure variables can be declared as const , and initialized with a structure constructor or initializer.

  • Initializers for const  declarations at global scope must be constant expressions

  • Constant Expressions :

    • SPIR-V specialization constants are expressed in GLSL as const  with the layout qualifier constant_id .

    • A constant expression  is one of

      • A literal value (e.g. 5  or true ).

      • A variable declared with the const  qualifier and an initializer, where the initializer is a constant expression. This includes both const  declared with a specialization-constant layout qualifier, e.g. layout ( constant_id  = …​), and those declared without a specialization-constant layout qualifier.

      • Built-in variables qualified as const .

      • An expression formed by an operator on operands that are all constant expressions, including getting an element of a constant array, or a member of a constant structure, or components of a constant vector. However, the lowest precedence operators of the sequence operator ( , ) and the assignment operators ( = , += , …​ ) are not included in the operators that can create a constant expression. Also, an array access with a specialization constant as an index does not result in a constant expression.

      • Valid use of the length () method on an explicitly sized object, whether or not the object itself is constant (implicitly sized or run-time sized arrays do not return a constant expression).

      • A constructor whose arguments are all constant expressions.

      • For non-specialization constants only: The value returned by certain built-in function calls whose arguments are all constant expressions, including at least the list below. Any other built-in function that does not access memory (not the texture lookup functions, image access, atomic counter, etc.), that has a non- void  return type, that has no out  parameter, and is not a noise function might also be considered a constant. When a function is called with an argument that is a specialization constant, the result is not a constant expression.

        • Angle and Trigonometric Functions

          • radians

          • degrees

          • sin

          • cos

          • asin

          • acos

        • Exponential Functions

          • pow

          • exp

          • log

          • exp2

          • log2

          • sqrt

          • inversesqrt

        • Common Functions

          • abs

          • sign

          • floor

          • trunc

          • round

          • ceil

          • mod

          • min

          • max

          • clamp

        • Geometric Functions

          • length

          • dot

          • normalize

      • Function calls to user-defined functions (non-built-in functions) cannot be used to form constant expressions.

    • A constant integral expression  is a constant expression that evaluates to a scalar signed or unsigned integer.

    • Constant expressions will be evaluated in an invariant way so as to create the same value in multiple shaders when the same constant expressions appear in those shaders.

    • Constant expressions respect the precise  and invariant  qualifiers but will be always be evaluated in an invariant way, independent of the use of such qualification, so as to create the same value in multiple shaders when the same constant expressions appear in those shaders.

    • Constant-expressions may be evaluated by a host platform, and are therefore not required to compute the same value that the same expression would evaluate to on the shader execution target. However, the host must use the same or greater precision than the target would use. When the precision qualification cannot be determined, the expression is evaluated at highp .

    • Specialization-constant expressions are never evaluated by the compiler front end, but instead retain the expression’s operations needed to evaluate them later on the host.

in
  • linkage into a shader from a previous stage, variable is copied in.

  • Shader input variables are declared with the in  storage qualifier. They form the input interface between previous stages of the API pipeline and the declaring shader. Input variables must be declared at global scope. Values from the previous pipeline stage are copied into input variables at the beginning of shader execution. It is a compile-time error to write to a variable declared as an input.

  • Only the input variables that are statically read need to be written by the previous stage; it is allowed to have superfluous declarations of input variables. This is shown in the following table.

|   |   |   |   |   |
|---|---|---|---|---|
|Treatment of Mismatched Input Variables|   |Consuming Shader (input variables)|   |   |
|No Declaration|Declared but no Static Use|Declared and Static Use|
|Generating Shader (output variables)|No Declaration|Allowed|Allowed|Link-Time Error|
|Declared but no Static Use|Allowed|Allowed|Allowed (values are undefined)|
|Declared and Static Use|Allowed|Allowed|Allowed (values are potentially undefined)|

  • Consumption errors are based on static use only. Compilation may generate a warning, but not an error, for any dynamic use the compiler can deduce that might cause consumption of undefined values.

  • See “ Built-In Variables ” for a list of the built-in input names.

  • Vertex shader input variables (or attributes) receive per-vertex data. It is a compile-time error to use auxiliary storage or interpolation qualifiers on a vertex shader input. The values copied in are established by the API or through the use of the layout identifier location .

  • It is a compile-time error to declare a vertex shader input with, or that contains, any of the following types:

  • Example declarations in a vertex shader:

in vec4 position;
in vec3 normal;
in vec2 texCoord[4];
  • It is expected that graphics hardware will have a small number of fixed vector locations for passing vertex inputs. Therefore, the OpenGL Shading Language defines each non-matrix input variable as taking up one such vector location. There is an implementation-dependent limit on the number of locations that can be used, and if this is exceeded it will cause a link-time error. (Declared input variables that are not statically used do not count against this limit.) A scalar input counts the same amount against this limit as a vec4 , so applications may want to consider packing groups of four unrelated float inputs together into a vector to better utilize the capabilities of the underlying hardware. A matrix input will use up multiple locations. The number of locations used will equal the number of columns in the matrix.

  • Tessellation control, evaluation, and geometry shader input variables get the per-vertex values written out by output variables of the same names in the previous active shader stage. For these inputs, centroid  and interpolation qualifiers are allowed, but have no effect. Since tessellation control, tessellation evaluation, and geometry shaders operate on a set of vertices, each input variable (or input block, see Interface Blocks  below) needs to be declared as an array. For example,

in float foo[]; // geometry shader input for vertex "out float foo"
  • Each element of such an array corresponds to one vertex of the primitive being processed. Each array can optionally have a size declared. For geometry shaders, the array size will be set by, (or if provided must be consistent with) the input layout  declaration(s) establishing the type of input primitive, as described later in “ Input Layout Qualifiers ”.

  • Some inputs and outputs are arrayed , meaning that for an interface between two shader stages either the input or output declaration requires an extra level of array indexing for the declarations to match. For example, with the interface between a vertex shader and a geometry shader, vertex shader output variables and geometry shader input variables of the same name must have matching types, except that the geometry shader will have one more array dimension than the vertex shader, to allow for vertex indexing. If such an arrayed interface variable is not declared with the necessary additional input or output array dimension, a link-time error will result. Geometry shader inputs, tessellation control shader inputs and outputs, and tessellation evaluation inputs all have an additional level of arrayness relative to other shader inputs and outputs. These inputs and outputs are known as per-vertex-arrayed  inputs and outputs. Component limits for arrayed interfaces (e.g. gl_MaxTessControlInputComponents ) are limits per vertex, not limits for the entire interface.

  • For non-arrayed interfaces (meaning array dimensionally stays the same between stages), it is a link-time error if the input variable is not declared with the same type, including array dimensionality, as the matching output variable.

  • The link-time type-matching rules apply to all declared input and output variables, whether or not they are used.

  • Additionally, tessellation evaluation shaders support per-patch input variables declared with the patch  and in  qualifiers. Per-patch input variables are filled with the values of per-patch output variables written by the tessellation control shader. Per-patch inputs may be declared as one-dimensional arrays, but are not indexed by vertex number. Applying the patch  qualifier to inputs can only be done in tessellation evaluation shaders. As with other input variables, per-patch inputs must be declared using the same type and qualification as per-patch outputs from the previous (tessellation control) shader stage. It is a compile-time error to use patch  with inputs in any other stage.

  • It is a compile-time error to declare a tessellation control, tessellation evaluation or geometry shader input with, or that contains, any of the following types:

  • Fragment shader inputs get per-fragment values, typically interpolated from a previous stage’s outputs. The auxiliary storage qualifiers centroid  and sample  can also be applied, as well as the interpolation qualifiers flat , noperspective , and smooth.

  • It is a compile-time error to declare a fragment shader input with, or that contains, any of the following types:

  • Fragment shader inputs that are, or contain, integral or double-precision floating-point types must be qualified with the interpolation qualifier flat .

  • Fragment inputs are declared as in the following examples:

in vec3 normal;
centroid in vec2 TexCoord;
noperspective in float temperature;
flat in vec3 myColor;
noperspective centroid in vec2 myTexCoord;
  • The fragment shader inputs form an interface with the last active shader in the vertex processing pipeline. For this interface, the last active shader stage output variables and fragment shader input variables of the same name must match in type and qualification, with a few exceptions: The storage qualifiers must, of course, differ (one is in  and one is out ). Also, interpolation qualification (e.g. flat ) and auxiliary qualification (e.g. centroid ) may differ. These mismatches are allowed between any pair of stages. When interpolation or auxiliary qualifiers do not match, those provided in the fragment shader supersede those provided in previous stages. If any such qualifiers are completely missing in the fragment shaders, then the default is used, rather than any qualifiers that may have been declared in previous stages. That is, what matters is what is declared in the fragment shaders, not what is declared in shaders in previous stages.

  • When an interface between shader stages is formed using shaders from two separate program objects, it is not possible to detect mismatches between inputs and outputs when the programs are linked. When there are mismatches between inputs and outputs on such interfaces, the values passed across the interface will be partially or completely undefined.

  • Shaders can ensure matches across such interfaces either by using input and output layout qualifiers (sections “ Input Layout Qualifiers ” and “ Output Layout Qualifiers ”) or by using identical input and output declarations of blocks or variables. Complete rules for interface matching are found in section 7.4.1 “Shader Interface Matching” of the OpenGL Specification .

  • Compute shaders do not permit user-defined input variables and do not form a formal interface with any other shader stage. See “ Compute Shader Special Variables ” for a description of built-in compute shader input variables. All other input to a compute shader is retrieved explicitly through image loads, texture fetches, loads from uniforms or uniform buffers, or other user supplied code. Redeclaration of built-in input variables in compute shaders is not permitted.

out
  • linkage out of a shader to a subsequent stage, variable is copied out.

  • Shader output variables are declared with the out  storage qualifier. They form the output interface between the declaring shader and the subsequent stages of the API pipeline. Output variables must be declared at global scope. During shader execution they will behave as normal unqualified global variables. Their values are copied out to the subsequent pipeline stage on shader exit. Only output variables that are read by the subsequent pipeline stage need to be written; it is allowed to have superfluous declarations of output variables.

  • There is not  an inout  storage qualifier for declaring a single variable name as both input and output to a shader. Also, a variable cannot be declared with both the in  and the out  qualifiers, this will result in a compile-time or link-time error. Output variables must be declared with different names than input variables. However, nesting an input or output inside an interface block with an instance name allows the same names with one referenced through a block instance name.

  • Vertex, tessellation evaluation, and geometry output variables output per-vertex data and are declared using the out  storage qualifier. Applying patch  to an output can only be done in a tessellation control shader. It is a compile-time error to use patch  on outputs in any other stage.

  • It is a compile-time error to declare a vertex, tessellation evaluation, tessellation control, or geometry shader output with, or that contains, any of the following types:

  • Individual outputs are declared as in the following examples:

out vec3 normal;
centroid out vec2 TexCoord;
invariant centroid out vec4 Color;
flat out vec3 myColor;
sample out vec4 perSampleColor;
  • These can also appear in interface blocks, as described in “ Interface Blocks ”. Interface blocks allow simpler addition of arrays to the interface from vertex to geometry shader. They also allow a fragment shader to have the same input interface as a geometry shader for a given vertex shader.

  • Tessellation control shader output variables are used to output per-vertex and per-patch data. Per-vertex output variables are arrayed (see arrayed  under “ Input Variables ”) and declared using the out  qualifier without the patch  qualifier. Per-patch output variables are declared using the patch  and out  qualifiers.

  • Since tessellation control shaders produce an arrayed primitive comprising multiple vertices, each per-vertex output variable (or output block, see Interface Blocks  below) needs to be declared as an array. For example,

out float foo[]; // feeds next stage input "in float foo[]"
  • Each element of such an array corresponds to one vertex of the primitive being produced. Each array can optionally have a size declared. The array size will be set by (or if provided must be consistent with) the output layout declaration(s) establishing the number of vertices in the output patch, as described later in “ Tessellation Control Outputs ”.

  • Each tessellation control shader invocation has a corresponding output patch vertex, and may assign values to per-vertex outputs only if they belong to that corresponding vertex. If a per-vertex output variable is used as an l-value, it is a compile-time or link-time error if the expression indicating the vertex index is not the identifier gl_InvocationID .

  • The order of execution of a tessellation control shader invocation relative to the other invocations for the same input patch is undefined unless the built-in function barrier () is used. This provides some control over relative execution order. When a shader invocation calls barrier (), its execution pauses until all other invocations have reached the same point of execution. Output variable assignments performed by any invocation executed prior to calling barrier () will be visible to any other invocation after the call to barrier () returns.

  • Because tessellation control shader invocations execute in undefined order between barriers, the values of per-vertex or per-patch output variables will sometimes be undefined. Consider the beginning and end of shader execution and each call to barrier () as synchronization points. The value of an output variable will be undefined in any of the three following cases:

    1. At the beginning of execution.

    2. At each synchronization point, unless

      • the value was well-defined after the previous synchronization point and was not written by any invocation since, or

      • the value was written by exactly one shader invocation since the previous synchronization point, or

      • the value was written by multiple shader invocations since the previous synchronization point, and the last write performed by all such invocations wrote the same value.

    3. When read by a shader invocation, if

      • the value was undefined at the previous synchronization point and has not been written by the same shader invocation since, or

      • the output variable is written to by any other shader invocation between the previous and next synchronization points, even if that assignment occurs in code following the read.

  • Fragment outputs output per-fragment data and are declared using the out  storage qualifier. It is a compile-time error to use auxiliary storage qualifiers or interpolation qualifiers in a fragment shader output declaration. It is a compile-time error to declare a fragment shader output with, or that contains, any of the following types:

  • Fragment outputs are declared as in the following examples:

out vec4 FragmentColor;
out uint Luminosity;
  • Compute shaders have no built-in output variables, do not support user-defined output variables and do not form a formal interface with any other shader stage. All outputs from a compute shader take the form of the side effects such as image stores and operations on atomic counters.

attribute
  • compatibility profile only and vertex language only; same as in when in a vertex shader

uniform
  • value does not change across the primitive being processed, uniforms form the linkage between a shader, API, and the application.

  • The uniform  qualifier is used to declare global variables whose values are the same across the entire primitive being processed. All uniform  variables are read-only and are initialized externally either at link time or through the API. The link-time initial value is either the value of the variable’s initializer, if present, or 0 if no initializer is present. Opaque types cannot have initializers, or a compile-time error results. When targeting Vulkan, it is a compile-time error to declare uniform  variables outside a block.

  • Example declarations are:

uniform vec4 lightPosition;
uniform vec3 color = vec3(0.7, 0.7, 0.2); // value assigned at link time
  • The uniform  qualifier can be used with any of the basic data types, or when declaring a variable whose type is a structure, or an array of any of these.

  • There is an implementation-dependent limit on the amount of storage for uniforms that can be used for each type of shader and if this is exceeded it will cause a compile-time or link-time error. Uniform variables that are declared but not used do not count against this limit. The number of user-defined uniform variables and the number of built-in uniform variables that are used within a shader are added together to determine whether available uniform storage has been exceeded.

  • Uniforms in shaders all share a single global name space when linked into a program or separable program. Hence, the types, initializers, and any location specifiers of all statically used uniform variables with the same name must match across all shaders that are linked into a single program. However it is not required to repeat the initializer or location specifier in all the linked shaders. While this single uniform name space is cross stage, a uniform variable name’s scope is per stage: If a uniform variable name is declared in one stage (e.g. a vertex shader) but not in another (e.g. a fragment shader), then that name is still available in the other stage for a different use.

varying
  • compatibility profile only and vertex and fragment languages only; same as out when in a vertex shader and same as in when in a fragment shader

buffer
  • value is stored in a buffer object, and can be read or written both by shader invocations and the API.

  • The buffer  qualifier is used to declare global variables whose values are stored in the data store of a buffer object bound through the API. Buffer variables can be read and written, with the underlying storage shared among all active shader invocations. Buffer variable memory reads and writes within a single shader invocation are processed in order. However, the order of reads and writes performed in one invocation relative to those performed by another invocation is largely undefined. Buffer variables may be qualified with memory qualifiers affecting how the underlying memory is accessed, as described in “ Memory Qualifiers ”.

  • The buffer  qualifier can be used to declare interface blocks (see “ Interface Blocks ”), which are then referred to as shader storage blocks. It is a compile-time error to declare buffer variables outside a block.

// use buffer to create a buffer block (shader storage block)
buffer BufferName { // externally visible name of buffer
    int count;      // typed, shared memory...
    ...             // ...
    vec4 v[];       // last member may be an array that is not sized
                    // until after link time (dynamically sized)
} Name;             // name of block within the shader
  • There are implementation-dependent limits on the number of shader storage blocks used for each type of shader, the combined number of shader storage blocks used for a program, and the amount of storage required by each individual shader storage block. If any of these limits are exceeded, it will cause a compile-time or link-time error.

  • If multiple shaders are linked together, then they will share a single global buffer variable name space. Hence, the types of all declared buffer variables with the same name must match across all shaders that are linked into a single program.

shared
  • compute shader only; variable storage is shared across all work items in a workgroup.

  • The shared  qualifier is used to declare global variables that have storage shared between all work items in a compute shader workgroup. Variables declared as shared  may only be used in compute shaders (see “ Compute Processor ”). Any other declaration of a shared  variable is a compile-time error. Shared variables are implicitly coherent (see “ Memory Qualifiers ”).

  • Variables declared as shared  may not have initializers and their contents are undefined at the beginning of shader execution. Any data written to shared  variables will be visible to other work items (executing the same shader) within the same workgroup.

  • In the absence of synchronization, the order of reads and writes to the same shared  variable by different invocations of a shader is not defined.

  • In order to achieve ordering with respect to reads and writes to shared  variables, control flow barriers must be employed using the barrier () function (see “ Shader Invocation Control Functions ”).

  • There is a limit to the total size of all variables declared as shared  in a single program. This limit, expressed in units of basic machine units may be determined by using the OpenGL API to query the value of MAX_COMPUTE_SHARED_MEMORY_SIZE.

Auxiliars

  • Some input and output qualified variables can be qualified with at most one additional auxiliary storage qualifier

  • Auxiliary storage qualifiers can only be used with the in  or out  storage qualifiers.

centroid
  • centroid-based interpolation

sample
  • per-sample interpolation

patch
  • per-tessellation-patch attributes

Interface Blocks

  • To implement our constant data we have to use an interface block. Interface blocks in shader code are used to group multiple global variables of the same <storage>  type.

  • In theory they aren’t necessarily solely for constant data.

layout(set = 0, binding = 0) uniform PerMeshData {
    vec4 camera_position;
    mat4 model_matrix;
    vec3 mesh_color;
} per_mesh_data;
  • Interface blocks are still global variables, and technically still follow the global variable format. However, the difference is that they have to be given a user-defined type.

  • They work exactly the same way as a struct  in GLSL/C++. For example, to access the model matrix in this interface block, you’d use per_mesh_data.model_matrix .

  • Input, output, uniform, and buffer variable declarations can be grouped into named interface blocks to provide coarser granularity backing than is achievable with individual declarations. They can have an optional instance name, used in the shader to reference their members. An output block of one programmable stage is backed by a corresponding input block in the subsequent programmable stage. A uniform block  is backed by the application with a buffer object. A buffer block , also known as a shader storage block , is also backed by the application with a buffer object. It is a compile-time error to have an input block in a vertex shader or an output block in a fragment shader. These uses are reserved for future use.

  • An interface block declaration is defined in the grammar as follows:

    • interface-block  :

    • type_qualifier   block-name   {   member-list   }   instance-nameopt   ;

    • block-name  :

    • identifier

    • member-list  :

    • member-declaration

    • member-declaration   member-list

    • member-declaration  :

    • layout-qualifieropt   qualifiersopt   type   declarators   ;

    • instance-name  :

    • identifier

    • identifier   array-specifier

  • Each of the above elements is discussed below.

  • First, an example,

uniform Transform {
    mat4 ModelViewMatrix;
    mat4 ModelViewProjectionMatrix;
    uniform mat3 NormalMatrix;      // allowed restatement of qualifier
    float Deformation;
};
  • The above establishes a uniform block named “Transform” with four uniforms grouped inside it.

  • type-qualifier  determines the interface of which the block will be a part and, optionally, additional qualifiers that are applied to the block. It is a compile-time error if it does not include one of the storage qualifiers in , out , uniform  or buffer . It may optionally include layout qualifiers , the auxiliary storage qualifier   patch , and the precise qualifier . buffer  blocks may additionally include Memory Qualifiers . It is a compile-time error to include any other qualifiers.

  • member-list  declares the variables that are to be grouped into the block. Types and declarators are the same as for other input, output, uniform, and buffer variable declarations outside blocks, with these exceptions:

  • Initializers are not allowed

  • Opaque types are not allowed

  • Structure definitions cannot be nested inside a block

  • Any of these would result in a compile-time error.

  • If no optional qualifier is used in a member-declaration, the qualification of the member includes all in , out , patch , uniform , or buffer  as determined by interface-qualifier . If optional qualifiers are used, they can include interpolation qualifiers, auxiliary storage qualifiers, precision qualifiers, and storage qualifiers and they must declare an input, output, or uniform member consistent with the interface qualifier of the block: Input variables, output variables, uniform variables, and buffer  members can only be in in  blocks, out  blocks, uniform  blocks, and shader storage blocks, respectively.

  • Repeating the in , out , patch , uniform , or buffer  interface qualifier for a member’s storage qualifier is optional. For example,

in Material {
    smooth in vec4 Color1; // legal, input inside in block
    smooth vec4 Color2;    // legal, 'in' inherited from 'in Material'
    vec2 TexCoord;         // legal, TexCoord is an input
    uniform float Atten;   // illegal, mismatched storage qualifier
};
  • Members of uniform  or buffer  storage blocks are always represented in memory as highp , regardless of any precision qualifier associated with the declaration. When values are read from or written to such variables they are converted to or from the declared precision as described in Conversion Between Precisions . Operations on the values within the shader will take place using the declared precision as normal.

  • A shader interface  is defined to be one of these:

  • All the uniform variables and uniform blocks declared in a program. This spans all compilation units linked together within one program.

  • All the buffer  blocks declared in a program.

  • The boundary between adjacent programmable pipeline stages: This spans all the outputs declared in all compilation units of the first stage and all the inputs declared in all compilation units of the second stage. Note that for the purposes of this definition, the fragment shader and the preceding shader are considered to have a shared boundary even though in practice, all values passed to the fragment shader first pass through the rasterizer and interpolator.

  • The block name ( block-name ) is used to match within shader interfaces: an output block of one pipeline stage will be matched to an input block with the same name in the subsequent pipeline stage. For uniform or shader storage blocks, the application uses the block name to identify the block. Block names have no other use within a shader beyond interface matching; it is a compile-time error to use a block name at global scope for anything other than as a block name (e.g. use of a block name for a global variable name or function name is currently reserved). It is a compile-time error to use the same block name for more than one block declaration in the same shader interface (as defined above) within one shader, even if the block contents are identical.

  • Matched block names within a shader interface (as defined above) must match in terms of having the same number of declarations with the same sequence of types and the same sequence of member names, as well as having matching member-wise layout qualification (see next section). Matched uniform or shader storage block names (but not input or output block names) must also either all be lacking an instance name or all having an instance name, putting their members at the same scoping level. When instance names are present on matched block names, it is allowed for the instance names to differ; they need not match for the blocks to match. Furthermore, if a matching block is declared as an array, then the array sizes must also match (or follow array matching rules for the shader interface between consecutive shader stages). Any mismatch will generate a link-time error. A block name is allowed to have different definitions in different shader interfaces within the same shader, allowing, for example, an input block and output block to have the same name.

  • If an instance name ( instance-name ) is not used, the names declared inside the block are scoped at the global level and accessed as if they were declared outside the block. If an instance name ( instance-name ) is used, then it puts all the members inside a scope within its own name space, accessed with the field selector ( . ) operator (analogously to structures). For example,

in Light {
    vec4 LightPos;
    vec3 LightColor;
};
in ColoredTexture {
    vec4 Color;
    vec2 TexCoord;
} Material;           // instance name
vec3 Color;           // different Color than Material.Color
vec4 LightPos;        // illegal, already defined
...
... = LightPos;       // accessing LightPos
... = Material.Color; // accessing Color in ColoredTexture block
  • Outside the shading language (i.e., in the API), members are similarly identified except the block name is always used in place of the instance name (API accesses are to shader interfaces, not to shaders). If there is no instance name, then the API does not use the block name to access a member, just the member name.

  • Within a shader interface, all declarations of the same global name must be for the same object and must match in type and in whether they declare a variable or member of a block with no instance name. The API also needs this name to uniquely identify an object in the shader interface. It is a link-time error if any particular shader interface contains

  • two different blocks, each having no instance name, and each having a member of the same name, or

  • a variable outside a block, and a block with no instance name, where the variable has the same name as a member in the block.

out Vertex {
    vec4 Position;  // API transform/feedback will use "Vertex.Position"
    vec2 Texture;
} Coords;           // shader will use "Coords.Position"
out Vertex2 {
    vec4 Color;     // API will use "Color"
    float Color2;
};
// in same program as Vertex2 above:
out Vertex3 {
    float Intensity;
    vec4 Color;     // ERROR, name collision with Color in Vertex2
};
float Color2;       // ERROR, collides with Color2 in Vertex2
  • For blocks declared as arrays, the array index must also be included when accessing members, as in this example

uniform Transform { // API uses "Transform[2]" to refer to instance 2
    mat4 ModelViewMatrix;
    mat4 ModelViewProjectionMatrix;
    vec4 a[]; // array will get implicitly sized
    float Deformation;
} transforms[4];
...
... = transforms[2].ModelViewMatrix; // shader access of instance 2
// API uses "Transform.ModelViewMatrix" to query an offset or other query
transforms[x].a.length(); // same length for 'a' for all x
Transform[x];             // illegal, must use 'transforms'
Transform.a.length();     // illegal, must use 'transforms'
...transforms[2].a[3]...  // if these are the only two dereferences of 'a',
...transforms[3].a[7]...  // then 'a' must be size 8, for all
transforms[x]
  • For uniform or shader storage blocks declared as an array, each individual array element corresponds to a separate buffer object bind range, backing one instance of the block. As the array size indicates the number of buffer objects needed, uniform and shader storage block array declarations must specify an array size. A uniform or shader storage block array can only be indexed with a dynamically uniform integral expression, otherwise results are undefined.

  • When using OpenGL API entry points to identify the name of an individual block in an array of blocks, the name string may include an array index (e.g. Transform[2] ). When using OpenGL API entry points to refer to offsets or other characteristics of a block member, an array index must not be specified (e.g. Transform.ModelViewMatrix ).

  • Tessellation control, tessellation evaluation and geometry shader input blocks must be declared as arrays and follow the array declaration and linking rules for all shader inputs for the respective stages. All other input and output block arrays must specify an array size.

  • There are implementation-dependent limits on the number of uniform blocks and the number of shader storage blocks that can be used per stage. If either limit is exceeded, it will cause a link-time error.

Layout Qualifiers

Input Layout Qualifiers

  • location = N

    • Assigns an input/output location index for vertex inputs, fragment outputs, or varyings. Used for linking stages and for vertex attribute bindings. (maps to SPIR-V Location ). docs.vulkan.org

  • binding = B  and set = S

    • Assign descriptor binding number and descriptor set index for opaque/descriptor resources (uniform buffers, storage buffers, samplers, images).

  • offset = N  and align = A

    • explicitly control byte offsets and alignment of members inside interface/UBO/SSBO blocks; they map to the SPIR-V Offset / ArrayStride / MatrixStride  decorations for struct members.

    • Use when you need explicit control over memory layout.

  • push_constant

    • (block qualifier)

    • Marks a uniform block as push constants.

    • They must be declared in the pipeline layout as push-constant ranges.

  • constant_id = N  (specialization constants)

    • Make a const  scalar into a SPIR-V specialization constant whose value can be supplied at pipeline creation time (allows driver optimization without full shader recompile).

  • Interpolation and qualifiers: flat , noperspective , centroid , sample , invariant

    • control interpolation rules or invariance; they map to SPIR-V interpolation/built-in decorations or decorations affecting interpolation

  • Location and Component Interface .

Location
layout(location = 3) in vec4 normal;
const int start = 6;
layout(location = start + 2) in vec4 v;
  • In this example the shader input normal  is assigned to vector location number 3 and v  is assigned location number 8. For vertex shader inputs, the location specifies the number of the vertex attribute from which input values are taken. For inputs of all other shader types, the location specifies a vector number that can be used to match against outputs from a previous shader stage, even if that shader is in a different program object.

  • The following language describes how many locations are consumed by a given type. However, geometry shader inputs, tessellation control shader inputs and outputs, and tessellation evaluation inputs all have an additional level of arrayness relative to other shader inputs and outputs. This outer array level is removed from the type before considering how many locations the type consumes.

  • If the declared input (after potentially removing an outer array level as just described above) is an array of size n  and each of the elements takes m  locations, it will be assigned m  * n  consecutive locations starting with the location specified. For example,

layout(location = 6) in vec4 colors[3];
  • will establish that the shader input colors  is assigned to vector location numbers 6, 7, and 8.

  • If the declared input is an n  Ă— m  matrix, it will be assigned multiple locations starting with the location specified. The number of locations assigned for each matrix will be the same as for an n -element array of m -component vectors. For example,

layout(location = 9) in mat4 transforms[2];
  • will establish that shader input transforms  is assigned to vector locations 9-16, with transforms[0]  being assigned to locations 9-12, and transforms[1]  being assigned to locations 13-16.

  • If the declared input is a structure or block, its members will be assigned consecutive locations in their order of declaration, with the first member assigned the location provided in the layout qualifier. For a structure, this process applies to the entire structure. It is a compile-time error to use a location  qualifier on a member of a structure. For a block, this process applies to the entire block, or until the first member is reached that has a location  layout qualifier.

  • When a block member is declared with a location  qualifier, its location comes from that qualifier; the member’s location  qualifier overrides the block-level declaration. Subsequent members are again assigned consecutive locations, based on the newest location, until the next member declared with a location  qualifier. The values used for locations do not have to be declared in increasing order.

  • If a block has no block-level location  layout qualifier, it is required that either all or none of its members have a location  layout qualifier, or a compile-time error results. For some blocks declared as arrays, the location  can only be applied at the block level: When a block is declared as an array where additional locations are needed for each member for each block array element, it is a compile-time error to specify locations on the block members. For arrayed  interfaces (those generally having an extra level of arrayness due to interface expansion), the outer array is stripped before applying this rule.

  • When generating SPIR-V, all in  and out  qualified user-declared (non built-in) variables and blocks (or all their members) must have a shader-specified location . Otherwise, a compile-time error is generated.

  • The locations consumed by block and structure members are determined by applying the rules above recursively as though the structure member were declared as an input variable of the same type. For example:

layout(location = 3) in struct S
{
    vec3 a;                      // gets location 3
    mat2 b;                      // gets locations 4 and 5
    vec4 c[2];                   // gets locations 6 and 7
    layout(location = 8) vec2 A; // ERROR, can't use on struct member
} s;
layout(location = 4) in block
{
    vec4 d;                      // gets location 4
    vec4 e;                      // gets location 5
    layout(location = 7) vec4 f; // gets location 7
    vec4 g;                      // gets location 8
    layout(location = 1) vec4 h; // gets location 1
    vec4 i;                      // gets location 2
    vec4 j;                      // gets location 3
    vec4 k;                      // ERROR, location 4 already used
};
  • The number of input locations available to a shader is limited. For vertex shaders, the limit is the advertised number of vertex attributes. For all other shaders, the limit is implementation-dependent and must be no less than one fourth of the advertised maximum input component count.

  • program will fail to link if any attached shader uses a location greater than or equal to the number of supported locations, unless device-dependent optimizations are able to make the program fit within available hardware resources.
    A program will fail to link if explicit location assignments leave the linker unable to find space for other variables without explicit assignments.
    For the purposes of determining if a non-vertex input matches an output from a previous shader stage, the location  layout qualifier (if any) must match.

Component
  • The component  qualifier allows the location to be more finely specified for scalars and vectors, down to the individual components within a location that are consumed. It is a compile-time error to use component  without also specifying the location  qualifier (order does not matter). The components within a location are 0, 1, 2, and 3. A variable or block member starting at component N  will consume components N , N+1 , N+2 , …​ up through its size. It is a compile-time error if this sequence of components gets larger than 3. A scalar double  will consume two of these components, and a dvec2  will consume all four components available within a location. A dvec3  or dvec4  can only be declared without specifying a component . A dvec3  will consume all four components of the first location and components 0 and 1 of the second location. This leaves components 2 and 3 available for other component-qualified declarations.

// a consumes components 2 and 3 of location 4
layout(location = 4, component = 2) in vec2 a;

// b consumes component 1 of location 4
layout(location = 4, component = 1) in float b;

// ERROR: c overflows component 3
layout(location = 3, component = 2) in vec3 c;

// d consumes components 2 and 3 of location 5
layout(location = 5, component = 2) in double d;

// ERROR: e overflows component 3 of location 6
layout(location = 6, component = 2) in dvec2 e;

// ERROR: f overlaps with g
layout(location = 7, component = 0) in vec2 f;
layout(location = 7, component = 1) in float g;

layout(location = 8) in dvec3 h; // components 0,1,2 and 3 of location 8
                                 // and components 0 and 1 of location 9
layout(location = 9, component = 2) in double i; // okay, compts 2 and 3
  • If the variable is an array, each element of the array, in order, is assigned to consecutive locations, but all at the same specified component within each location. For example:

// component 3 is consumed in each of 6 locations
layout(location = 2, component = 3) in float d[6];
  • That is, location 2 component 3 will hold d[0] , location 3 component 3 will hold d[1] , …​, up through location 7 component 3 holding d[5] .

  • This allows packing of two arrays into the same set of locations:

// e consumes beginning (components 0, 1 and 2) of each of 6 slots
layout(location = 0, component = 0) in vec3 e[6];

// f consumes last component of the same 6 slots
layout(location = 0, component = 3) in float f[6];
  • If applying this to an array of arrays, all levels of arrayness are removed to get to the elements that are assigned per location to the specified component. These non-arrayed elements will fill the locations in the order specified for arrays of arrays in “ Arrays ”.

  • It is a compile-time error to apply the component  qualifier to a matrix, a structure, a block, or an array containing any of these. It is a compile-time error to use component  1 or 3 as the beginning of a double  or dvec2 . It is a link-time error to specify different components for the same variable within a program.

Location Aliasing
  • Location aliasing  is causing two variables or block members to have the same location number. Component aliasing  is assigning the same (or overlapping) component numbers for two location aliases. (Recall if component  is not used, components are assigned starting with 0.) With one exception, location aliasing is allowed only if it does not cause component aliasing; it is a compile-time or link-time error to cause component aliasing. Further, when location aliasing, the aliases sharing the location must have the same underlying numerical type and bit width (floating-point or integer, 32-bit versus 64-bit, etc.) and the same auxiliary storage and interpolation qualification. The one exception where component aliasing is permitted is when targeting OpenGL for two input variables (not block members) to a vertex shader, which are allowed to have component aliasing. This vertex-variable component aliasing is intended only to support vertex shaders where each execution path accesses at most one input per aliased component. Implementations are permitted, but not required, to generate link-time errors if they detect that every path through the vertex shader executable accesses multiple inputs aliased to any single component.